﻿/*
 *   Evaluator.cs
 *   Copyright (c) 2011 - BitFox Software and Ricardo Federico Villafañes
 *   Contact: federvillaf@hotmail.com
 *   
 *   Licensing: This product is covered by Microsoft Reciprocal License (MS-RL)
 *              see https://bitfoxeval.codeplex.com/license
*/

using System;
using System.Reflection;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics.CodeAnalysis;

using BitFox.Evaluator.Functions;

namespace BitFox.Evaluator
{
    /// <summary>
    /// This class represents an expression evaluator.
    /// </summary>
    [SuppressMessage("Microsoft.Naming", "CA1724")]
    public class Evaluator
    {
        /// <summary>
        /// The context with settings.
        /// </summary>
        CalculatorContext context;

        /// <summary>
        /// The context that contains some settings applicable elsewhere.
        /// </summary>
        public CalculatorContext Context
        {
            get { return context; }
            set
            {
                if (value == null) 
                    throw new ArgumentNullException("value");
                context = value;
            }
        }

        /// <summary>
        /// The list of named variables that are local to the evaluator.
        /// </summary>
        public Dictionary<string, object> Variables 
        { 
            get; 
            private set; 
        }

        /// <summary>
        /// The list of functions available for expressions
        /// </summary>
        internal Dictionary<string, FunctionDescriptor> Functions 
        { 
            get; 
            private set; 
        }

        /// <summary>
        /// The list of name's solvers that are available for the evaluator.
        /// </summary>
        /// <remarks>
        /// The order of solvers is important. When a solver do solve a given request, the rest of
        /// solvers are not invoked.
        /// </remarks>
        public Collection<NameHandlingDelegate> NameSolvers { get; private set; }

        /// <summary>
        /// Helper method used to resolve a given variable's name (or array reference)
        /// </summary>
        /// <param name="name">The variable/array name</param>
        /// <param name="indexes">If <b>name is an array, this collection of ints is used to indicate which array's 
        /// element is requested.</b></param>
        /// <returns>The requested item's value.</returns>
        public virtual object Resolve(string name, int[] indexes)
        {
            // Try to solve the request using the registered solvers.
            if (NameSolvers.Count != 0)
            {
                NameRequest req = new NameRequest(name, indexes);
                foreach (NameHandlingDelegate solver in NameSolvers)
                {
                    solver.Invoke(req);
                    if (req.Resolved) return req.Value;
                }
            }

            // See if the requested variable is in local list of local variables.
            string uname = name.FoxToUpper();
            if (!Variables.ContainsKey(uname))
            {
                uname = name;
                if (!Variables.ContainsKey(uname))
                    throw new BitFoxException(ErrorCode.VariableNotFound, "The variable '" + name + "' is not defined");
            }

            // Return an scalar object
            object res = Variables[uname];
            if (indexes != null && indexes.Length > 0)
                return ((Array)res).GetValue(indexes);
            return res;
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="context">The context with settings</param>
        /// <remarks>During construction, this method automatically add to 'Functions' list the list of functions
        /// declared in 'BuiltinFunctions.Functions' static property. After construction, the caller is able to
        /// replace or delete builtin functions.</remarks>
        public Evaluator(CalculatorContext context)
        {
            Variables = new Dictionary<string, object>();
            NameSolvers = new Collection<NameHandlingDelegate>();
            Functions = new Dictionary<string, FunctionDescriptor>(BuiltinFunctions.Functions);
            this.Context = context;
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <remarks>During construction, this method automatically add to 'Functions' list the list of functions
        /// declared in 'BuiltinFunctions.Functions' static property. After construction, the caller is able to
        /// replace or delete builtin functions.</remarks>
        public Evaluator()
            : this(new CalculatorContext())
        {
        }

        /// <summary>
        /// This method compiles a given NAMED expression.
        /// </summary>
        /// <param name="exp">The expression to compile</param>
        /// <param name="consumed">The number of bytes consumed from 'exp' argument.</param>
        /// <returns>The compiled expression, ready to run</returns>
        /// <remarks>
        /// A NAMED EXPRESSION is a string that can be expressed as unquoted, or as a reference to an expression.
        /// Examples:
        ///     1.  USE file                ('file' is a string and is treated as it)
        ///     2.  USE "file"              (Same as 1 but, as the string is quoted, it can contain embedded white spaces).
        ///     3.  USE 'file'              (Same as 2)
        ///     4.  USE (myPath + 'file')   (The exp between parenths is evaluated and returned as a single string)
        /// </remarks>
        [SuppressMessage("Microsoft.Design", "CA1021")]
        public Expression CompileNamedString(string exp, out int consumed)
        {
            Expression res = new Expression(this);
            consumed = res.CompileNamedString(exp);
            return res;
        }

        /// <summary>
        /// This method compiles a given expression (as a string).
        /// </summary>
        /// <param name="exp">The expression to compile</param>
        /// <param name="consumed">The number of bytes consumed from 'exp' argument.</param>
        /// <returns>The compiled expression, ready to run</returns>
        [SuppressMessage("Microsoft.Design", "CA1021")]
        public Expression Compile(string exp, out int consumed)
        {
            Expression res = new Expression(this);
            consumed = res.Compile(exp);
            return res;
        }

        /// <summary>
        /// This method compiles a given expression (as a string).
        /// </summary>
        /// <param name="exp">The expression to compile</param>
        /// <return>The compiled expression, ready to run</return>
        public Expression Compile(string exp)
        {
            int c;
            if (exp == null) throw new ArgumentNullException("exp");

            var res = Compile(exp, out c);
            if (exp.Substring(c).Trim().Length > 0)
                throw new BitFoxException(ErrorCode.MissingToken, "Garbage at end of expression");
            return res;
        }
    }

    /// <summary>
    /// Delegate for a possible operation with variable's names (best known as name 'solvers')
    /// </summary>
    /// <param name="request">The operation requested to the solver</param>
    public delegate void NameHandlingDelegate(NameRequest request);

}
